package nl.rug.ai.believer.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import nl.rug.ai.believer.model.exception.DuplicateException;

/**
 * A container class for equivalence classes. An equivalence class is a group of worlds (or a single world) which can
 * not be distinguished by an agent on grounds of knowledge. (Within an equivalence class, an agent could however
 * have preferences (beliefs) for one or more worlds.)
 * 
 * This class does not make validity checks on agents/worlds, State is responsible for keeping this class valid.
 * @author S1849700
 *
 */
public class EquivalenceClass {
	
	private Agent agent; // The agent to which this equiv-class belongs.
	/**
	 * Each element of this orderedBeliefs ArrayList contains an ArrayList with Worlds that are (on a belief level) 
	 * equivalent. The elements on the upper level (orderedBeliefs) are sorted from most believed (element 0) to least
	 * believed (element size-1). 
	 */
	private ArrayList<ArrayList<World>> orderedBeliefs = new ArrayList<ArrayList<World>>();
	private ArrayList<World> knowledgeList = new ArrayList<World>();
	private ArrayList<Relation> relationList = new ArrayList<Relation>();
	
	EquivalenceClass(Agent agent) {
		this.agent = agent;
	}

	/**
	 * Add a world to this equivalence class. (All worlds of an equivalence class are on a knowledge level equivalent 
	 * to an agent.
	 * @param world the world to add
	 */
	private void addWorld(World world) {
		if (hasWorld(world)) {
			return; // we already have this world, no need for duplicates.
		}
		this.knowledgeList.add(world);
	}
	
	/**
	 * Add a relation to this equivalence Class. By adding a relation, knowledge and belief of this equiv class are 
	 * automatically updated.
	 * @param relation the relation to add
	 */
	void addRelation(Relation relation) {
		if (relationList.contains(relation)) {
			throw new DuplicateException("Duplicate relation for agent: " + relation.getAgent().getName() +
					" from world: " + relation.getFromWorld().getName() + " to world: " + 
					relation.getToWorld().getName());
		}
		if (!relation.getAgent().equals(agent)) {
			throw new IllegalArgumentException("A relation of the wrong agent was added to an equivalence class.");
		}
		relationList.add(relation);
		addWorld(relation.getFromWorld());
		addWorld(relation.getToWorld());
		updateBeliefs();
	}
	
	/**
	 * This method creates a new list of believed worlds.
	 */
	private void updateBeliefs() {
		Integer occurances[] = getWorldOccurance();
		Integer sortedOccurances[] = occurances.clone();
		Arrays.sort(sortedOccurances, Collections.reverseOrder()); // sort from high to low.
		ArrayList<ArrayList<World>> orderedBList = new ArrayList<ArrayList<World>>();
		int lastSeen = -1;
		for (int current : sortedOccurances) {
			if (current == lastSeen) {
				continue;
			}
			ArrayList<World> wList = new ArrayList<World>();
			for (int i = 0; i < occurances.length; i++) {
				if (occurances[i] == current) {
					wList.add(knowledgeList.get(i));
				}
			}
			orderedBList.add(wList);
			lastSeen = current;
		}
		orderedBeliefs = orderedBList;
	}
	
	/**
	 * Determine how many relations in this equiv class have each world as their "toWorld". Sorted in the same way
	 * as knowledgeList.
	 * @return an array containing this information (index matches the knowledgeList ArrayList).
	 */
	private Integer[] getWorldOccurance() {
		Integer result[] = new Integer[knowledgeList.size()]; 
		for (int i = 0; i < result.length; i++) {
			result[i] = new Integer(0);
		}
		for (Relation rel : relationList) {
			int nr = knowledgeList.indexOf((rel.getToWorld()));
			if (nr == -1) {
				throw new IllegalStateException("An instance Equivalence Class has become corrupt and cannot continue" +
						" to operate properly.");
			}
			result[nr]++;
		}
		return result;
	}	
	
	/**
	 * Check if a world exists in this equivalence class.
	 * @param world the world to check
	 * @return true if exists
	 */
	boolean hasWorld(World world) {
		return knowledgeList.contains(world);
	}
	
	/**
	 * Returns the agent object associated with this equivalence class
	 * @return the agent object to who this class belongs
	 */
	Agent getAgent() {
		return agent;
	}
	
	/**
	 * This method returns a list of worlds that constitutes the "knowledge" in this equivalence class.
	 * @return array of worlds
	 */
	World[] getKnowledge() {
		World[] myWorlds = new World[knowledgeList.size()];
		knowledgeList.toArray(myWorlds);
		return myWorlds;
	}
	
	/**
	 * This method returns a list of worlds that constitutes the "belief" in this equivalence class.
	 * @return array of worlds
	 */
	World[] getBelief() {
		if (orderedBeliefs.isEmpty()) {
			return new World[0];
		}
		World[] myWorlds = new World[orderedBeliefs.get(0).size()];
		orderedBeliefs.get(0).toArray(myWorlds);
		return myWorlds;
	}
	
	/**
	 * This method returns the list of relations which form this equivalence class.
	 * @return array of worlds
	 */
	Relation[] getRelations() {
		Relation[] myRelations = new Relation[relationList.size()];
		relationList.toArray(myRelations);
		return myRelations;
	}
	
	/**
	 * this method returns an ArrayList of ArrayLists containing worlds, which represents the full
	 * ordered belief as present in this equivalence class.
	 * @return the ordered belief
	 */
	ArrayList<ArrayList<World>> getOrderedBeliefs() {
		return orderedBeliefs;
	}
}
